#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <netinet/in.h>


/** VERBOSE pre processor macro */
#define VERBOSE(format, ...)    \
do { if(verbose == 1) { \
        printf("%s [%s, %s(), line: %d]: ", applicationName, __FILE__, __FUNCTION__, __LINE__); \
        printf(format, ## __VA_ARGS__); } \
} while (0)

/**
 * this struct is for easily passing the application options given by command line agruments
 */
struct applicationOptions {
	char* 	server;
	char* 	port;   /* port is string as well (needed by getaddrinfo()) */
	char* 	user;
	char* 	image;
	char* 	message;
	int     help;
};

/**
 * GLOBALS
 */
static char* applicationName;	/* Holding the application name (execution name) */
static int verbose;             /* Verbose mode */

/**
 * This function prints the usage message / help
 */
void print_usage(void) {
	printf("usage: %s options\n", applicationName);
	printf("options:\n");
	printf("\t-s, --server <server>\tfull qualified domain name or IP address of the server\n");
	printf("\t-p, --port <port>\twell-known port of the server [0..65535]\n");
        printf("\t-u, --user <name>\tname of the posting user\n");
        printf("\t-i, --image <URL>\tURL pointing to an image of the posting user\n");
        printf("\t-m, --message <message> message to be added to the bulletin board\n");
        printf("\t-v, --verbose\t\tverbose output\n");
        printf("\t-h, --help\n");
}

/**
 * This function parses the command line arguments given with execution
 * and saves them inside the application_options structure
 *
 * @param [in] argc		argument count
 * @param [in] argv		arguments
 * @param [out] cfg		the configuration structure
 */
void parse_arguments(int argc, char* argv[], struct applicationOptions *cfg) {
    int c = 0;              /* The current option */
    int optionIndex = 0;    /* The index of currently parsed option */
    int noparam = 1;        /* Used to check if no option was passed at all (short and long) */
    static struct option longopts[] = {
            { "server", required_argument,  NULL, 's' },
            { "port",   required_argument,  NULL, 'p' },
            { "user",   required_argument,  NULL, 'u' },
            { "image",  required_argument,  NULL, 'i' },
            { "message",required_argument,  NULL, 'm' },
            { "verbose",no_argument,        NULL, 'v' },
            { "help",   no_argument,        NULL, 'h' }
    };

    /* Parse all command line options */
	while((c = getopt_long(argc, argv, "s:p:u:i:m:vh", longopts, &optionIndex)) != -1) {
	    /* Set this to zero after first option found */
        noparam = 0;
        /* Do configuration regarding to option */
        switch(c) {
            case 's': cfg->server = optarg; break;
            case 'p': cfg->port = optarg; break;
            case 'u': cfg->user = optarg; break;
            case 'i': cfg->image = optarg; break;
            case 'm': cfg->message = optarg; break;
            case 'v': verbose = 1; break;
            case 'h': cfg->help = 1; break;
            default:  cfg->help = 1; break;
        }
	}

    /* If no option was passed (while-loop never entered),
       or more arguments than options where given, display the usage message */
    if(noparam == 1 || optind < argc) cfg->help = 1;
}

/**
 * This function constructs the message sent to the server
 *
 * @param [in] cfg the client configuration containing the message parts
 * @param [in,out] messageBuffer an existing string variable to pass the message back
 * @return int message size of the constructed message
 */
int construct_message(struct applicationOptions cfg, char* messageBuffer) {
    int message_length = 0;

    if(cfg.image != NULL) {
        message_length = sprintf(messageBuffer, "user=%s\nimg=%s\n%s\n", cfg.user, cfg.image, cfg.message);
    } else {
        message_length = sprintf(messageBuffer, "user=%s\n%s\n", cfg.user, cfg.message);
    }

    return message_length;
}

/**
 * This function handles the sending part of the client
 *
 * @param [in] fd_socket the TCP socket used for sending; Must be in connected mode.
 * @param [in] message the message to send
 * @param [in] len the message size
 * @return int the amount of data sent
 */
int send_message(int fd_socket, char* message, int len) {
    int status = 0, amount_sent = 0;

    /* Try writing the message to the server - even in parts if necessary */
    while((status = write(fd_socket, message+amount_sent, len-amount_sent)) > 0)
        amount_sent += status;

    /* Did everything work fine? */
    if(status == -1) {
        fprintf(stderr, "%s: Cannot write to socket\n", applicationName);
        exit(1);
    }
    /* Return length send */
    return amount_sent;
}

/**
 * This function handles receiving the server response and filling it to a buffer
 *
 * @param [in] fd_socket the tcp socket
 * @param [out] buffer the response buffer to be filled
 * @return int the length of received data
 */
int receive_response(int fd_socket, char** buffer) {
    char temp_buffer[1024];                 /* Temporary buffer for sending in chunks */
    int status = 0;                         /* Status code while receiving */
    int new_length = 0, overall_length = 0; /* Variables counting the received length */

    while(1) {
        /* Try reading the response */
        status = recv(fd_socket, temp_buffer, sizeof(temp_buffer), 0);
        /* Was still any data received? */
        if(status <= 0) break;
        /* Count the length received */
        new_length = overall_length + status;
        /* Resize buffer */
        *buffer = realloc(*buffer, new_length);
        /* Was resizing successful? */
        if(*buffer == NULL) {
            fprintf(stderr, "%s: Could not reallocate memory\n", applicationName);
            exit(1);
        }
        /* Append response */
        memcpy(*buffer+overall_length, temp_buffer, status);
        /* Count overall length */
        overall_length = new_length;
    }

    /* Did everything work well? */
    if(status == -1) {
        fprintf(stderr, "%s: Cannot read from socket\n", applicationName);
        exit(1);
    }

    return overall_length;
}

/**
 * This function is used for saving a file to the file system
 *
 * @param [in] filename the filename to use
 * @param [in] data the content of the file
 * @param [in] len the size of the file
 */
void save_file(char* filename, char* data, int len) {
    FILE *fp;   /* The handle used to open a file stream */
    int chunk_size = 256;
    int chunks = len / chunk_size;
    int last_chunk_size = len % chunk_size;
    int i;

    VERBOSE("Opening file \"%s\" for writing of %d bytes in %d chunks @%d bytes and a last remainder chunk @%d bytes ...\n", filename, len, chunks, chunk_size, last_chunk_size);

    /* Try opening the file in binary write mode */
    if((fp = fopen(filename, "wb")) == NULL) {
        fprintf(stderr, "%s: Cannot open file for writing\n", applicationName);
        exit(1);
    }

    VERBOSE("Opened file \"%s\" for writing of %d bytes in %d chunks @%d bytes and a last remainder chunk @%d bytes ...\n", filename, len, chunks, chunk_size, last_chunk_size);

    /* Copy all full chunks to file */
    for(i = 0; i<chunks; i++) {
        if(fwrite(data + (i*chunk_size), sizeof(char), chunk_size, fp) == 0 || fflush(fp) == EOF) {
            fprintf(stderr, "%s: Error while writing data to the file\n", applicationName);
            exit(1);
        }
        VERBOSE("Copied chunk %d @%d bytes\n", i, chunk_size);
    }
    /* Copy the remainder chunk to file */
    if(fwrite(data + (i*chunk_size), sizeof(char), last_chunk_size, fp)  == 0 || fflush(fp) == EOF) {
        fprintf(stderr, "%s: Error while writing last chunk to the file\n", applicationName);
        exit(1);
    }
    VERBOSE("Copied chunk %d @%d bytes\n", i, last_chunk_size);

    /* Close the file stream - and eventually flush remaining data */
    if(fclose(fp) == EOF) {
        fprintf(stderr, "%s: Error while finishing writing the file\n", applicationName);
        exit(1);
    }

    VERBOSE("Closed file \"%s\"\n", filename);

    return;
}

/**
 * This function parses the response received from the server.
 * It handles splitting the response into records, and saving it to separate files.
 *
 * @param [in] response the server response to parse
 * @return int 0 on successful parsing, -1 on error
 */
int parse_response(char* response, int length) {
    char* parser_position;                     /* This is the pointer to the current position */
    char status[16];                           /* This variable holds the status code received */
    char file_name[256]; char file_len[32];    /* Temporary variables holding file information */
    char *endptr;
    int file_start=0;                          /* Offsets defining file data position from pointer */
    int parsed = 0;                            /* parsed is used to check if the end is reached */

    /* Point onto the string to parse */
    parser_position = response;
    /* Read the response code sent by the server */
    if(sscanf(parser_position, "status=%s", status) == 0)
        VERBOSE("Status code received not well formed!\n");
    else
        VERBOSE("Received well formed status code: %s\n", status);

    /* Count parsed characters */
    parsed = 7 + strlen(status) + 1;
    /* Begin parsing the files */
    while(parsed < length) { // TODO !!!
        /* Move pointer */
        parser_position = response + parsed;
        /* Try reading file name and file length */
        if(sscanf(parser_position, "file=%s\nlen=%s\n", file_name, file_len) != 2) {
            fprintf(stderr, "%s: Could not parse server response\n", applicationName);
            exit(1);
        }
        VERBOSE("Received file (%s) with total length of %s bytes\n", file_name, file_len);
        /* Calculate file start end end offset */
        file_start = 5 + strlen(file_name) + 5 + strlen(file_len) + 1;
        /* Count parsed characters */
        parsed += file_start + strtol(file_len, &endptr, 10);
        /* Save the received file to the system */
        save_file(file_name, parser_position + file_start, strtol(file_len, &endptr, 10));
    }

    return 0;
}

/**
 * This function contains the network communication code to the server
 *
 * @param [in] cfg the client configuration containing everything needed.
 */
void start_communication(struct applicationOptions cfg) {

    int fd_socket, status, messagesize;
    char buffer[1024];
    char* response = NULL;
    struct addrinfo server_hints;   /* This describes our "virtual" connection to the server */
    struct addrinfo *server_info;   /* This will hold our server specific address (ipv4/6) */

    struct sockaddr_in* server_addr_ip4;
    char ip4_addr[INET_ADDRSTRLEN];
    unsigned short int ip4_port;

    memset(&server_hints, 0, sizeof server_hints);  /* This is needed for padding sockaddr_in to length of sockaddr */
    server_hints.ai_family = AF_UNSPEC;             /* Don't care for ipv4 or ipv6 */
    server_hints.ai_socktype = SOCK_STREAM;         /* TCP stream socket */
    server_hints.ai_flags = AI_ADDRCONFIG;          /* WHAT DOES THIS DO?!? NEEDED WITHOUT NOT WORKING --> OTHER WAY COMPLETELY WITHOUT HINTING << not good*/

    /* After filling with hints get the real server address info server_info */
    status = getaddrinfo(cfg.server, cfg.port, &server_hints, &server_info);

    /* Did everything work fine? */
    if(status != 0) {
        fprintf(stderr, "%s: getaddrinfo error: %s\n", applicationName, gai_strerror(status));
        exit(1);
    }

    server_addr_ip4 = (struct sockaddr_in*) server_info->ai_addr;   // TODO
    inet_ntop(AF_INET, &(server_addr_ip4)->sin_addr, ip4_addr, INET_ADDRSTRLEN); /* converts sin_addr to readable string for verbose */
    ip4_port = htons(server_addr_ip4->sin_port);                                 /* converts sin_port to readable string for verbose */

    VERBOSE("Obtained IPv4 address %s, port number %hu for server %s and port %s\n", ip4_addr, ip4_port, cfg.server, cfg.port);

    /* Create the new socket */
    fd_socket = socket(server_info->ai_family, server_info->ai_socktype, server_info->ai_protocol);

    /* Did it work? */
    if(fd_socket == 0) {
        fprintf(stderr, "%s: Cannot open socket\n", applicationName);
        exit(1);
    }

    VERBOSE("Created IPv4 SOCK_STREAM socket\n");

    /* Connect to the server */
    status = connect(fd_socket, server_info->ai_addr, server_info->ai_addrlen);

    /* Did it work? */
    if(status != 0) {
        fprintf(stderr, "%s: Cannot connect to server - Connection refused\n", applicationName);
        exit(1);
    }

    VERBOSE("Connected to port %s (%hu) of server %s (%s)\n", cfg.port, ip4_port, cfg.server, ip4_addr);

    /* Free server_info - not needed anymore */
    freeaddrinfo(server_info);

    /* Construct the message to send */
    messagesize = construct_message(cfg, buffer);

    VERBOSE("Going to send the following message consisting of %d bytes ...\n%s", messagesize, buffer);

    /* Send the message to the server */
    if(send_message(fd_socket, buffer, messagesize) < messagesize) {
        fprintf(stderr, "%s: Could not send complete message to server\n", applicationName);
        exit(1);
    }

    VERBOSE("Sent request user=\"%s\", img_url=\"%s\", message=\"%s\" to server %s (%s)\n", cfg.user, cfg.image, cfg.message, cfg.server, ip4_addr);

    VERBOSE("Trying to flush file pointer associated with socket ... NOT YET IMPLEMENTED!!!! <- SCHEINBAR WIRD EIN EIGENER FILE HANDLE VERWENDET DER AUF DEN SOCKET AUFSETZT\n");
    
    VERBOSE("Trying to shutdown socket asociated with file pointer ...\n");
    /* Client finished sending - close one way */
    if(shutdown(fd_socket, SHUT_WR) == -1) {
        fprintf(stderr, "%s: Could not close sending stream\n", applicationName);
        exit(1);
    }

    VERBOSE("Trying to close file pointer asociated with socket ... NOT YET IMPLEMENTED!!!!\n");

    VERBOSE("Closed write part of socket\n");

    /* Initialize the respone variable */
    /*
    response = (char*) malloc(sizeof(char));

    if(response == NULL) {
        fprintf(stderr, "%s: Could not allocate memory for response buffer\n", applicationName);
        exit(1);
    }
    */

    /* Receive the response and length */
    status = receive_response(fd_socket, &response);

    /* Beginn parsing the response */
    parse_response(response, status);

    /* Close the socket completely successfull communication */
    close(fd_socket);

    VERBOSE("Closed read part of socket\n");

    return;
}

/**
 * main function
 */
int main(int argc, char* argv[]) {
    /* Initialize client options */
    struct applicationOptions cfg = { NULL, NULL, NULL, NULL, NULL, 0 };

    applicationName = *argv;            /* Get application name for usage print */
    parse_arguments(argc, argv, &cfg);  /* Parse command line arguments */

    VERBOSE("Using the following options: server=\"%s\" port=\"%s\", user=\"%s\", img_url=\"%s\", message=\"%s\"\n", cfg.server, cfg.port, cfg.user, cfg.image, cfg.message);

    /* Check client options for completeness */
    if( cfg.help == 1 || cfg.server == NULL || cfg.port == NULL || cfg.user == NULL || cfg.message == NULL) {
        print_usage();
    } else {
        start_communication(cfg);
    }

    return 0;
}


